/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.arc.level;

import com.arc.message.SerializableVector;
import com.jme3.math.Vector3f;
import com.jme3.network.serializing.Serializable;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Adam
 */
@Serializable
public class NetworkedNode {
    //Finds the root of a tree
    public static NetworkedNode findRoot(NetworkedNode node){
        if(node.parent != null){
            findRoot(node.getParent());
        }
        
        return node;
    }
    
    //Merges the nodeFrom tree into the nodeTo tree
    public static void mergeTrees(NetworkedNode nodeFrom, NetworkedNode nodeTo) throws Exception{
        if(nodeFrom == null) return;
        if(nodeTo.parent != null)
            throw new Exception("mergeTrees nodeTo error, nodeTo must be the root node!");
       
        if(nodeTo.findNode(nodeFrom) == null){
            System.out.println(nodeFrom.getName() + " is not in nodeTo... adding it..");
            NetworkedNode toParent; 
            if((toParent = nodeTo.findNode(nodeFrom.parent)) != null){
                toParent.attachChild(new NetworkedNode(nodeFrom));
                System.out.println("Attaching " + nodeFrom.getName() + " to " + toParent.getName());
            }else{
                //mergeTrees(nodeFrom.parent, nodeTo);                
                System.out.println("Couldn't find parent of " + nodeFrom.getName() + " in toNode which is " + nodeFrom.getParent().getName());
            }
        }
                
        for(NetworkedNode child : nodeFrom.getChildren()){
            mergeTrees(child, nodeTo);
        }
    }
    
    public static void mergeTrees(Node nodeFrom, Node nodeTo) throws Exception{
        if(nodeFrom == null) return;
        if(nodeTo.getParent() != null)
            throw new Exception("mergeTrees nodeTo error, nodeTo must be the root node!");
       
        if(nodeTo.findNode(nodeFrom) == null){
            System.out.println(nodeFrom.getName() + " is not in nodeTo... adding it..");
            NetworkedNode toParent; 
            if((toParent = nodeTo.findNode(nodeFrom.parent)) != null){
                toParent.attachChild(new NetworkedNode(nodeFrom));
                System.out.println("Attaching " + nodeFrom.getName() + " to " + toParent.getName());
            }else{
                //mergeTrees(nodeFrom.parent, nodeTo);                
                System.out.println("Couldn't find parent of " + nodeFrom.getName() + " in toNode which is " + nodeFrom.getParent().getName());
            }
        }
                
        for(NetworkedNode child : nodeFrom.getChildren()){
            mergeTrees(child, nodeTo);
        }
    }
    
    protected NetworkedTransform localTransform;
    protected NetworkedTransform worldTransform;
    protected NetworkedNode parent;
    protected String name;
    protected ArrayList<NetworkedNode> children = new ArrayList<NetworkedNode>(); 
    
    public NetworkedNode(){
        localTransform = new NetworkedTransform();
        worldTransform = new NetworkedTransform();
        name = "default";
    }
    
    public NetworkedNode(Node node){
        localTransform = new NetworkedTransform(node.getLocalTransform());
        worldTransform = new NetworkedTransform(node.getWorldTransform());
        name = node.getName();
        
        for(Spatial child : node.getChildren()){
            NetworkedNode n = new NetworkedNode((Node)child);
            this.attachChild(n);
        }
    }
    
    public NetworkedNode(String name){
        this();
        this.name = name;
    }
    
    public NetworkedNode(NetworkedNode copy){
        this.name = copy.name;
        localTransform = copy.localTransform;
        worldTransform = copy.worldTransform;
    }
    
    public void setLocalTranslation(Vector3f translation){
        localTransform.setTranslation(translation);
    }
    
    public void setLocalTranslation(SerializableVector translation){        
        localTransform.setTranslation(translation.toVector3f());
    }
    
    public Vector3f getLocalTranslation(){ return localTransform.getTranslation(); }
    
    public String getName(){ return name; }
    
    //Converts the networkednode to a Node
    public Node toNode(){
        Node n = new Node(name);
        n.setLocalTransform(localTransform.toTransform());        
        return n;
    }
    
    //Converts a node to a node tree maintaining child/parent relationships
    public Node toNodeTree(){
        return createTree(this, null);
    }
    
    private Node createTree(NetworkedNode netNode, Node node){
        node = netNode.toNode();
        
        for(NetworkedNode child : netNode.getChildren()){
            node.attachChild(createTree(child, node));
        }
        
        return node;
    }
    
    //Finds a node in the node tree
    public NetworkedNode findNode(NetworkedNode node){
        NetworkedNode root = findRoot(this);
        return findRecursive(node, root, null);
    }
    
    private NetworkedNode findRecursive(NetworkedNode lookFor, NetworkedNode node, NetworkedNode result){
        if(!lookFor.equals(node)){
            for(NetworkedNode child : node.getChildren()){
                return findRecursive(lookFor, child, result);
            }
        }else{
            result = node;
        }
        
        return result;
    }
    
    public List<NetworkedNode> getChildren(){
        return children;
    }
    
    public void attachChild(NetworkedNode node) {
        node.parent = this;
        this.children.add(node);
    }
    
    public void detachChild(NetworkedNode node){
        if(children.contains(node))
        {
            node.parent = null;
            children.remove(node);
        }
    }
    
    public boolean equals(NetworkedNode n){
        if(n == null || n.getClass() != this.getClass() || n.getName() != this.getName())
            return false;
        if(n == this)
            return true;
        
        return true;            
    }
    
    public boolean equals(Node n){
        if(n == null || n.getName() != this.getName())
            return false;
        
        return true;            
    }

    public NetworkedNode getParent() {
        return parent;
    }
}
